NexusPi Git Node
WebSocketServer.cpp f0aee0671e84b627f90883c0f1fca8f0d4dbc9da (f0aee067) Text, 16.38 KB
Tff7b72#Tff7b72if defined(ENABLE_WEBSOCKETS) && __has_include(<WiFi.h>)
Tff7b72#Tff7b72include T8b949e"WebSocketServer.h"
Tff7b72#Tff7b72include T8b949e"native/ws_shims/base64.h"
Tff7b72#Tff7b72include T8b949e"native/ws_shims/sha1.h"
Tff7b72#Tff7b72include T8b949e<Arduino.h>
Tff7b72#Tff7b72include T8b949e<cstdio>
Tff7b72#Tff7b72include T8b949e<cstring>
Tff7b72#Tff7b72if defined(PORTDUINO)
Tff7b72#Tff7b72include T8b949e<arpa/inet.h>
Tff7b72#Tff7b72include T8b949e<errno.h>
Tff7b72#Tff7b72include T8b949e<fcntl.h>
Tff7b72#Tff7b72include T8b949e<netinet/in.h>
Tff7b72#Tff7b72include T8b949e<signal.h>
Tff7b72#Tff7b72include T8b949e<sys/socket.h>
Tff7b72#Tff7b72include T8b949e<unistd.h>
Tff7b72#Tff7b72endif
Tff7b72namespace Tb4b4b4{
Tff7b72constexpr Tffa657char Te6edf3kWsGuidTb4b4b4[Tb4b4b4] Tff7b72= Ta5d6ff"Ta5d6ff258EAFA5-E914-47DA-95CA-C5AB0DC85B11Ta5d6ff"Tb4b4b4;
T8b949e// ASCII case-insensitive prefix compare — used for header name lookup.
Tffa657bool Td2a8ffieq_prefixTb4b4b4(Tff7b72const Tffa657uint8_tTff7b72* Te6edf3hayTb4b4b4, Tffa657size_t Te6edf3hay_lenTb4b4b4,
Tff7b72const Tffa657charTff7b72* Te6edf3needleTb4b4b4) Tb4b4b4{
Tffa657size_t Te6edf3i Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72while Tb4b4b4(Te6edf3needleTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3i Tff7b72>Tff7b72= Te6edf3hay_lenTb4b4b4) Tff7b72return Tffa657falseTb4b4b4;
Tffa657uint8_t Te6edf3a Tff7b72= Te6edf3hayTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4;
Tffa657uint8_t Te6edf3b Tff7b72= Tff7b72static_castTff7b72<Tffa657uint8_tTff7b72>Tb4b4b4(Te6edf3needleTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3a Tff7b72>Tff7b72= Ta5d6ff'Ta5d6ffATa5d6ff' Tff7b72&Tff7b72& Te6edf3a Tff7b72<Tff7b72= Ta5d6ff'Ta5d6ffZTa5d6ff'Tb4b4b4) Te6edf3a Tff7b72|Tff7b72= T79c0ff0x20Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3b Tff7b72>Tff7b72= Ta5d6ff'Ta5d6ffATa5d6ff' Tff7b72&Tff7b72& Te6edf3b Tff7b72<Tff7b72= Ta5d6ff'Ta5d6ffZTa5d6ff'Tb4b4b4) Te6edf3b Tff7b72|Tff7b72= T79c0ff0x20Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3a Tff7b72!Tff7b72= Te6edf3bTb4b4b4) Tff7b72return Tffa657falseTb4b4b4;
Tff7b72+Tff7b72+Te6edf3iTb4b4b4;
Tb4b4b4}
Tff7b72return Tffa657trueTb4b4b4;
Tb4b4b4}
T8b949e// Find header line `name:` (case-insensitive) in `buf[0..len)`. Returns a
T8b949e// pointer to the start of the value (after the colon, leading SP trimmed)
T8b949e// and writes the value length into *out_len. Returns nullptr if absent.
Tff7b72const Tffa657uint8_tTff7b72* Td2a8fffind_headerTb4b4b4(Tff7b72const Tffa657uint8_tTff7b72* Te6edf3bufTb4b4b4, Tffa657size_t Te6edf3lenTb4b4b4,
Tff7b72const Tffa657charTff7b72* Te6edf3nameTb4b4b4,
Tffa657size_tTff7b72* Te6edf3out_lenTb4b4b4) Tb4b4b4{
Tff7b72const Tffa657size_t Te6edf3name_len Tff7b72= Te6edf3stdTff7b72:Tff7b72:Te6edf3strlenTb4b4b4(Te6edf3nameTb4b4b4)Tb4b4b4;
Tffa657size_t Te6edf3line_start Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657size_t Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72+ T79c0ff1 Tff7b72< Te6edf3lenTb4b4b4; Tff7b72+Tff7b72+Te6edf3iTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3bufTb4b4b4[Te6edf3iTb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\rTa5d6ff' Tff7b72&Tff7b72& Te6edf3bufTb4b4b4[Te6edf3i Tff7b72+ T79c0ff1Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\nTa5d6ff'Tb4b4b4) Tb4b4b4{
Tff7b72const Tffa657size_t Te6edf3line_len Tff7b72= Te6edf3i Tff7b72- Te6edf3line_startTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3line_len Tff7b72> Te6edf3name_len Tff7b72+ T79c0ff1
Tff7b72&Tff7b72& Te6edf3ieq_prefixTb4b4b4(Te6edf3buf Tff7b72+ Te6edf3line_startTb4b4b4, Te6edf3line_lenTb4b4b4, Te6edf3nameTb4b4b4)
Tff7b72&Tff7b72& Te6edf3bufTb4b4b4[Te6edf3line_start Tff7b72+ Te6edf3name_lenTb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff:Ta5d6ff'Tb4b4b4) Tb4b4b4{
Tffa657size_t Te6edf3v0 Tff7b72= Te6edf3line_start Tff7b72+ Te6edf3name_len Tff7b72+ T79c0ff1Tb4b4b4;
Tff7b72while Tb4b4b4(Te6edf3v0 Tff7b72< Te6edf3i Tff7b72&Tff7b72& Tb4b4b4(Te6edf3bufTb4b4b4[Te6edf3v0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff Ta5d6ff' Tff7b72|Tff7b72| Te6edf3bufTb4b4b4[Te6edf3v0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\tTa5d6ff'Tb4b4b4)Tb4b4b4) Tff7b72+Tff7b72+Te6edf3v0Tb4b4b4;
Tffa657size_t Te6edf3v1 Tff7b72= Te6edf3iTb4b4b4;
Tff7b72while Tb4b4b4(Te6edf3v1 Tff7b72> Te6edf3v0 Tff7b72&Tff7b72& Tb4b4b4(Te6edf3bufTb4b4b4[Te6edf3v1T79c0ff-1Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff Ta5d6ff' Tff7b72|Tff7b72| Te6edf3bufTb4b4b4[Te6edf3v1T79c0ff-1Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\tTa5d6ff'Tb4b4b4)Tb4b4b4) Tff7b72-Tff7b72-Te6edf3v1Tb4b4b4;
Tff7b72*Te6edf3out_len Tff7b72= Te6edf3v1 Tff7b72- Te6edf3v0Tb4b4b4;
Tff7b72return Te6edf3buf Tff7b72+ Te6edf3v0Tb4b4b4;
Tb4b4b4}
Te6edf3line_start Tff7b72= Te6edf3i Tff7b72+ T79c0ff2Tb4b4b4;
Tff7b72+Tff7b72+Te6edf3iTb4b4b4; T8b949e// skip \n
Tb4b4b4}
Tb4b4b4}
Tff7b72return Tffa657nullptrTb4b4b4;
Tb4b4b4}
Tb4b4b4} T8b949e// namespace
Te6edf3WebSocketServerTff7b72:Tff7b72:Tff7b72~Te6edf3WebSocketServerTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72#Tff7b72if defined(PORTDUINO)
Tff7b72if Tb4b4b4(Te6edf3listen_fd_ Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72:Tff7b72:Te6edf3closeTb4b4b4(Te6edf3listen_fd_Tb4b4b4)Tb4b4b4;
Te6edf3listen_fd_ Tff7b72= T79c0ff-1Tb4b4b4;
Tb4b4b4}
Tff7b72#Tff7b72endif
Tb4b4b4}
Tffa657void Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3beginTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72#Tff7b72if defined(PORTDUINO)
T8b949e// Native (Portduino): we bypass WiFiServer entirely because Portduino's
T8b949e// WiFiServer::begin() hardcodes INADDR_ANY, leaving no way to bind on
T8b949e// loopback only. Open and bind our own listening socket so we honor
T8b949e// bind_public_.
Te6edf3signalTb4b4b4(Te6edf3SIGPIPETb4b4b4, Te6edf3SIG_IGNTb4b4b4)Tb4b4b4; T8b949e// a disconnecting WS peer shouldn't kill us
Te6edf3listen_fd_ Tff7b72= Tff7b72:Tff7b72:Te6edf3socketTb4b4b4(Te6edf3AF_INETTb4b4b4, Te6edf3SOCK_STREAMTb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3listen_fd_ Tff7b72< T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3stdTff7b72:Tff7b72:Te6edf3fprintfTb4b4b4(Te6edf3stderrTb4b4b4, Ta5d6ff"Ta5d6ff[ws] socket(): %sTffea00\nTa5d6ff"Tb4b4b4, Te6edf3stdTff7b72:Tff7b72:Te6edf3strerrorTb4b4b4(Te6edf3errnoTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tffa657int Te6edf3yes Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3setsockoptTb4b4b4(Te6edf3listen_fd_Tb4b4b4, Te6edf3SOL_SOCKETTb4b4b4, Te6edf3SO_REUSEADDRTb4b4b4, Tff7b72&Te6edf3yesTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3yesTb4b4b4)Tb4b4b4)Tb4b4b4;
T8b949e// Close-on-exec so a re-exec'd daemon (native_reboot::perform) doesn't
T8b949e// inherit this listener and fail to re-bind with EADDRINUSE. The explicit
T8b949e// shutdown() in the reboot path closes the fd too — this is belt and
T8b949e// suspenders.
Tffa657int Te6edf3fdflags Tff7b72= Te6edf3fcntlTb4b4b4(Te6edf3listen_fd_Tb4b4b4, Te6edf3F_GETFDTb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3fdflags Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Te6edf3fcntlTb4b4b4(Te6edf3listen_fd_Tb4b4b4, Te6edf3F_SETFDTb4b4b4, Te6edf3fdflags Tff7b72| Te6edf3FD_CLOEXECTb4b4b4)Tb4b4b4;
Tff7b72const Tffa657uint32_t Te6edf3bind_addr Tff7b72= Te6edf3bind_public_ Tff7b72? Te6edf3INADDR_ANY Tff7b72: Te6edf3INADDR_LOOPBACKTb4b4b4;
Tff7b72const Tffa657charTff7b72* Te6edf3bind_label Tff7b72= Te6edf3bind_public_ Tff7b72? Ta5d6ff"Ta5d6ff0.0.0.0Ta5d6ff" Tff7b72: Ta5d6ff"Ta5d6ff127.0.0.1Ta5d6ff"Tb4b4b4;
Te6edf3sockaddr_in Te6edf3addrTb4b4b4;
Te6edf3stdTff7b72:Tff7b72:Te6edf3memsetTb4b4b4(Tff7b72&Te6edf3addrTb4b4b4, T79c0ff0Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3addrTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3addrTb4b4b4.Te6edf3sin_family Tff7b72= Te6edf3AF_INETTb4b4b4;
Te6edf3addrTb4b4b4.Te6edf3sin_addrTb4b4b4.Te6edf3s_addr Tff7b72= Te6edf3htonlTb4b4b4(Te6edf3bind_addrTb4b4b4)Tb4b4b4;
Te6edf3addrTb4b4b4.Te6edf3sin_port Tff7b72= Te6edf3htonsTb4b4b4(Te6edf3port_Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72:Tff7b72:Te6edf3bindTb4b4b4(Te6edf3listen_fd_Tb4b4b4, Tff7b72reinterpret_castTff7b72<Te6edf3sockaddrTff7b72*Tff7b72>Tb4b4b4(Tff7b72&Te6edf3addrTb4b4b4)Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3addrTb4b4b4)Tb4b4b4) Tff7b72< T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3stdTff7b72:Tff7b72:Te6edf3fprintfTb4b4b4(Te6edf3stderrTb4b4b4, Ta5d6ff"Ta5d6ff[ws] bind(%s:%u): %sTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3bind_labelTb4b4b4, Te6edf3port_Tb4b4b4, Te6edf3stdTff7b72:Tff7b72:Te6edf3strerrorTb4b4b4(Te6edf3errnoTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72:Tff7b72:Te6edf3closeTb4b4b4(Te6edf3listen_fd_Tb4b4b4)Tb4b4b4;
Te6edf3listen_fd_ Tff7b72= T79c0ff-1Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Tff7b72:Tff7b72:Te6edf3listenTb4b4b4(Te6edf3listen_fd_Tb4b4b4, T79c0ff1Tb4b4b4) Tff7b72< T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3stdTff7b72:Tff7b72:Te6edf3fprintfTb4b4b4(Te6edf3stderrTb4b4b4, Ta5d6ff"Ta5d6ff[ws] listen(): %sTffea00\nTa5d6ff"Tb4b4b4, Te6edf3stdTff7b72:Tff7b72:Te6edf3strerrorTb4b4b4(Te6edf3errnoTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72:Tff7b72:Te6edf3closeTb4b4b4(Te6edf3listen_fd_Tb4b4b4)Tb4b4b4;
Te6edf3listen_fd_ Tff7b72= T79c0ff-1Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tffa657int Te6edf3flags Tff7b72= Te6edf3fcntlTb4b4b4(Te6edf3listen_fd_Tb4b4b4, Te6edf3F_GETFLTb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4;
Te6edf3fcntlTb4b4b4(Te6edf3listen_fd_Tb4b4b4, Te6edf3F_SETFLTb4b4b4, Te6edf3flags Tff7b72| Te6edf3O_NONBLOCKTb4b4b4)Tb4b4b4;
Te6edf3stdTff7b72:Tff7b72:Te6edf3fprintfTb4b4b4(Te6edf3stderrTb4b4b4, Ta5d6ff"Ta5d6ff[ws] listening on %s:%u%sTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3bind_labelTb4b4b4, Te6edf3port_Tb4b4b4,
Te6edf3bind_public_ Tff7b72? Ta5d6ff"Ta5d6ff (PUBLIC — no auth)Ta5d6ff" Tff7b72: Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72else
T8b949e// ESP32: stock Arduino WiFiServer. Always binds 0.0.0.0 (intended —
T8b949e// the console is served over the device's WiFi AP). bind_public_ is
T8b949e// ignored.
Te6edf3server_Tb4b4b4.Te6edf3beginTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72endif
Tb4b4b4}
Tffa657void Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3serviceTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Tear down a half-dead client first.
Tff7b72if Tb4b4b4(Te6edf3state_ Tff7b72!Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3WAITING Tff7b72&Tff7b72& Tff7b72!Te6edf3client_Tb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3teardownTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72switch Tb4b4b4(Te6edf3state_Tb4b4b4) Tb4b4b4{
Tff7b72case Tff7b72StateTff7b72:Tff7b72:Tff7b72WAITINGTb4b4b4: Te6edf3drive_acceptTb4b4b4(Tb4b4b4)Tb4b4b4; Tff7b72breakTb4b4b4;
Tff7b72case Tff7b72StateTff7b72:Tff7b72:Tff7b72HANDSHAKINGTb4b4b4: Te6edf3drive_handshakeTb4b4b4(Tb4b4b4)Tb4b4b4; Tff7b72breakTb4b4b4;
Tff7b72case Tff7b72StateTff7b72:Tff7b72:Tff7b72OPENTb4b4b4: Te6edf3drive_frameTb4b4b4(Tb4b4b4)Tb4b4b4; Tff7b72breakTb4b4b4;
Tff7b72case Tff7b72StateTff7b72:Tff7b72:Tff7b72CLOSINGTb4b4b4: Te6edf3teardownTb4b4b4(Tb4b4b4)Tb4b4b4; Tff7b72breakTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tffa657void Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3drive_acceptTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72#Tff7b72if defined(PORTDUINO)
Tff7b72if Tb4b4b4(Te6edf3listen_fd_ Tff7b72< T79c0ff0Tb4b4b4) Tff7b72returnTb4b4b4;
Te6edf3sockaddr_in Te6edf3cliTb4b4b4;
Tffa657socklen_t Te6edf3clen Tff7b72= Tff7b72sizeofTb4b4b4(Te6edf3cliTb4b4b4)Tb4b4b4;
Tffa657int Te6edf3s Tff7b72= Tff7b72:Tff7b72:Te6edf3acceptTb4b4b4(Te6edf3listen_fd_Tb4b4b4, Tff7b72reinterpret_castTff7b72<Te6edf3sockaddrTff7b72*Tff7b72>Tb4b4b4(Tff7b72&Te6edf3cliTb4b4b4)Tb4b4b4, Tff7b72&Te6edf3clenTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3s Tff7b72< T79c0ff0Tb4b4b4) Tb4b4b4{
T8b949e// EAGAIN/EWOULDBLOCK on a non-blocking listener = no pending
T8b949e// client right now; ignore. Other errno values are unexpected
T8b949e// but non-fatal — we'll try again next tick.
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tffa657int Te6edf3flags Tff7b72= Te6edf3fcntlTb4b4b4(Te6edf3sTb4b4b4, Te6edf3F_GETFLTb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4;
Te6edf3fcntlTb4b4b4(Te6edf3sTb4b4b4, Te6edf3F_SETFLTb4b4b4, Te6edf3flags Tff7b72| Te6edf3O_NONBLOCKTb4b4b4)Tb4b4b4;
Tffa657int Te6edf3cflags Tff7b72= Te6edf3fcntlTb4b4b4(Te6edf3sTb4b4b4, Te6edf3F_GETFDTb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cflags Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Te6edf3fcntlTb4b4b4(Te6edf3sTb4b4b4, Te6edf3F_SETFDTb4b4b4, Te6edf3cflags Tff7b72| Te6edf3FD_CLOEXECTb4b4b4)Tb4b4b4;
Te6edf3client_ Tff7b72= Te6edf3WiFiClientTb4b4b4(Te6edf3sTb4b4b4)Tb4b4b4;
Te6edf3client_addr_ Tff7b72= Te6edf3cliTb4b4b4;
Te6edf3state_ Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3HANDSHAKINGTb4b4b4;
Te6edf3req_len_ Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72#Tff7b72else
Te6edf3WiFiClient Te6edf3pending Tff7b72= Te6edf3server_Tb4b4b4.Te6edf3availableTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3pendingTb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72returnTb4b4b4;
Te6edf3client_ Tff7b72= Te6edf3pendingTb4b4b4;
Te6edf3state_ Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3HANDSHAKINGTb4b4b4;
Te6edf3req_len_ Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72#Tff7b72endif
Tb4b4b4}
Tffa657void Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3drive_handshakeTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72while Tb4b4b4(Tffa657trueTb4b4b4) Tb4b4b4{
Tffa657int Te6edf3b Tff7b72= Te6edf3client_Tb4b4b4.Te6edf3readTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3b Tff7b72< T79c0ff0Tb4b4b4) Tff7b72returnTb4b4b4; T8b949e// no more data right now
Tff7b72if Tb4b4b4(Te6edf3req_len_ Tff7b72>Tff7b72= Te6edf3REQ_BUF_CAPTb4b4b4) Tb4b4b4{
Tff7b72const Tffa657charTff7b72* Te6edf3resp Tff7b72= Ta5d6ff"Ta5d6ffHTTP/1.1 400 Bad RequestTffea00\rTffea00\nTffea00\rTffea00\nTa5d6ff"Tb4b4b4;
Te6edf3client_Tb4b4b4.Te6edf3writeTb4b4b4(Tff7b72reinterpret_castTff7b72<Tff7b72const Tffa657uint8_tTff7b72*Tff7b72>Tb4b4b4(Te6edf3respTb4b4b4)Tb4b4b4,
Te6edf3stdTff7b72:Tff7b72:Te6edf3strlenTb4b4b4(Te6edf3respTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3teardownTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Te6edf3req_buf_Tb4b4b4[Te6edf3req_len_Tff7b72+Tff7b72+Tb4b4b4] Tff7b72= Tff7b72static_castTff7b72<Tffa657uint8_tTff7b72>Tb4b4b4(Te6edf3bTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3req_len_ Tff7b72>Tff7b72= T79c0ff4
Tff7b72&Tff7b72& Te6edf3req_buf_Tb4b4b4[Te6edf3req_len_ Tff7b72- T79c0ff4Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\rTa5d6ff'
Tff7b72&Tff7b72& Te6edf3req_buf_Tb4b4b4[Te6edf3req_len_ Tff7b72- T79c0ff3Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\nTa5d6ff'
Tff7b72&Tff7b72& Te6edf3req_buf_Tb4b4b4[Te6edf3req_len_ Tff7b72- T79c0ff2Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\rTa5d6ff'
Tff7b72&Tff7b72& Te6edf3req_buf_Tb4b4b4[Te6edf3req_len_ Tff7b72- T79c0ff1Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\nTa5d6ff'Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3finish_handshakeTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3teardownTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tffa657bool Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3finish_handshakeTb4b4b4(Tb4b4b4) Tb4b4b4{
Tffa657size_t Te6edf3key_len Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72const Tffa657uint8_tTff7b72* Te6edf3key Tff7b72= Te6edf3find_headerTb4b4b4(Te6edf3req_buf_Tb4b4b4, Te6edf3req_len_Tb4b4b4,
Ta5d6ff"Ta5d6ffSec-WebSocket-KeyTa5d6ff"Tb4b4b4, Tff7b72&Te6edf3key_lenTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3key Tff7b72|Tff7b72| Te6edf3key_len Tff7b72=Tff7b72= T79c0ff0 Tff7b72|Tff7b72| Te6edf3key_len Tff7b72> T79c0ff64Tb4b4b4) Tb4b4b4{
Tff7b72const Tffa657charTff7b72* Te6edf3resp Tff7b72=
Ta5d6ff"Ta5d6ffHTTP/1.1 400 Bad RequestTffea00\rTffea00\nTa5d6ff"
Ta5d6ff"Ta5d6ffContent-Length: 0Tffea00\rTffea00\nTffea00\rTffea00\nTa5d6ff"Tb4b4b4;
Te6edf3client_Tb4b4b4.Te6edf3writeTb4b4b4(Tff7b72reinterpret_castTff7b72<Tff7b72const Tffa657uint8_tTff7b72*Tff7b72>Tb4b4b4(Te6edf3respTb4b4b4)Tb4b4b4,
Te6edf3stdTff7b72:Tff7b72:Te6edf3strlenTb4b4b4(Te6edf3respTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72return Tffa657falseTb4b4b4;
Tb4b4b4}
T8b949e// accept = base64( SHA1( key || GUID ) )
Tffa657uint8_t Te6edf3concatTb4b4b4[T79c0ff64 Tff7b72+ T79c0ff36Tb4b4b4]Tb4b4b4;
Te6edf3stdTff7b72:Tff7b72:Te6edf3memcpyTb4b4b4(Te6edf3concatTb4b4b4, Te6edf3keyTb4b4b4, Te6edf3key_lenTb4b4b4)Tb4b4b4;
Te6edf3stdTff7b72:Tff7b72:Te6edf3memcpyTb4b4b4(Te6edf3concat Tff7b72+ Te6edf3key_lenTb4b4b4, Te6edf3kWsGuidTb4b4b4, T79c0ff36Tb4b4b4)Tb4b4b4;
Tffa657uint8_t Te6edf3digestTb4b4b4[T79c0ff20Tb4b4b4]Tb4b4b4;
Te6edf3ws_protoTff7b72:Tff7b72:Te6edf3sha1Tb4b4b4(Te6edf3concatTb4b4b4, Te6edf3key_len Tff7b72+ T79c0ff36Tb4b4b4, Te6edf3digestTb4b4b4)Tb4b4b4;
Tffa657char Te6edf3acceptTb4b4b4[T79c0ff32Tb4b4b4]Tb4b4b4;
Tffa657size_t Te6edf3accept_len Tff7b72= Te6edf3ws_protoTff7b72:Tff7b72:Te6edf3base64_encodeTb4b4b4(Te6edf3digestTb4b4b4, T79c0ff20Tb4b4b4, Te6edf3acceptTb4b4b4)Tb4b4b4;
Tffa657char Te6edf3respTb4b4b4[T79c0ff256Tb4b4b4]Tb4b4b4;
Tffa657int Te6edf3n Tff7b72= Te6edf3stdTff7b72:Tff7b72:Te6edf3snprintfTb4b4b4(Te6edf3respTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3respTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffHTTP/1.1 101 Switching ProtocolsTffea00\rTffea00\nTa5d6ff"
Ta5d6ff"Ta5d6ffUpgrade: websocketTffea00\rTffea00\nTa5d6ff"
Ta5d6ff"Ta5d6ffConnection: UpgradeTffea00\rTffea00\nTa5d6ff"
Ta5d6ff"Ta5d6ffSec-WebSocket-Accept: %.*sTffea00\rTffea00\nTa5d6ff"
Ta5d6ff"Tffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Tff7b72static_castTff7b72<Tffa657intTff7b72>Tb4b4b4(Te6edf3accept_lenTb4b4b4)Tb4b4b4, Te6edf3acceptTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3n Tff7b72<Tff7b72= T79c0ff0 Tff7b72|Tff7b72| Te6edf3n Tff7b72>Tff7b72= Tff7b72static_castTff7b72<Tffa657intTff7b72>Tb4b4b4(Tff7b72sizeofTb4b4b4(Te6edf3respTb4b4b4)Tb4b4b4)Tb4b4b4) Tff7b72return Tffa657falseTb4b4b4;
Te6edf3client_Tb4b4b4.Te6edf3writeTb4b4b4(Tff7b72reinterpret_castTff7b72<Tff7b72const Tffa657uint8_tTff7b72*Tff7b72>Tb4b4b4(Te6edf3respTb4b4b4)Tb4b4b4,
Tff7b72static_castTff7b72<Tffa657size_tTff7b72>Tb4b4b4(Te6edf3nTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3state_ Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3OPENTb4b4b4;
Te6edf3msg_opcode_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3msg_len_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3reset_frame_rxTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72if defined(PORTDUINO)
T8b949e// ESP32 path intentionally silent — the Arduino-ESP32 Serial is the
T8b949e// KISS host link in host mode, so any unsolicited bytes there would
T8b949e// corrupt the framing. Add a Serial log only behind a debug flag if
T8b949e// needed in the future.
Te6edf3stdTff7b72:Tff7b72:Te6edf3fprintfTb4b4b4(Te6edf3stderrTb4b4b4, Ta5d6ff"Ta5d6ff[ws] client connected from %s:%uTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3inet_ntoaTb4b4b4(Te6edf3client_addr_Tb4b4b4.Te6edf3sin_addrTb4b4b4)Tb4b4b4,
Tb4b4b4(Tffa657unsignedTb4b4b4)Te6edf3ntohsTb4b4b4(Te6edf3client_addr_Tb4b4b4.Te6edf3sin_portTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72endif
Tff7b72return Tffa657trueTb4b4b4;
Tb4b4b4}
Tffa657void Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3reset_frame_rxTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3rx_phase_ Tff7b72= Te6edf3RxPhaseTff7b72:Tff7b72:Te6edf3HEAD_0Tb4b4b4;
Te6edf3rx_opcode_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3rx_fin_ Tff7b72= Tffa657falseTb4b4b4;
Te6edf3rx_is_ctrl_ Tff7b72= Tffa657falseTb4b4b4;
Te6edf3rx_masked_ Tff7b72= Tffa657falseTb4b4b4;
Te6edf3rx_ext_remaining_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3rx_mask_pos_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3rx_payload_pos_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3rx_payload_total_ Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4}
Tffa657void Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3drive_frameTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72while Tb4b4b4(Tffa657trueTb4b4b4) Tb4b4b4{
Tffa657int Te6edf3b Tff7b72= Te6edf3client_Tb4b4b4.Te6edf3readTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3b Tff7b72< T79c0ff0Tb4b4b4) Tff7b72returnTb4b4b4;
Tff7b72const Tffa657uint8_t Te6edf3byte Tff7b72= Tff7b72static_castTff7b72<Tffa657uint8_tTff7b72>Tb4b4b4(Te6edf3bTb4b4b4)Tb4b4b4;
Tff7b72switch Tb4b4b4(Te6edf3rx_phase_Tb4b4b4) Tb4b4b4{
Tff7b72case Tff7b72RxPhaseTff7b72:Tff7b72:Tff7b72HEAD_0Tb4b4b4: Tb4b4b4{
Te6edf3rx_fin_ Tff7b72= Tb4b4b4(Te6edf3byte Tff7b72& T79c0ff0x80Tb4b4b4) Tff7b72!Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3rx_opcode_ Tff7b72= Te6edf3byte Tff7b72& T79c0ff0x0fTb4b4b4;
Te6edf3rx_is_ctrl_ Tff7b72= Tb4b4b4(Te6edf3rx_opcode_ Tff7b72& T79c0ff0x8Tb4b4b4) Tff7b72!Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3rx_is_ctrl_Tb4b4b4) Tb4b4b4{
T8b949e// Control frames must NOT be fragmented.
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3rx_fin_Tb4b4b4) Tb4b4b4{ Te6edf3send_closeTb4b4b4(Te6edf3CLOSE_PROTOTb4b4b4)Tb4b4b4; Tff7b72returnTb4b4b4; Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Data frame: TEXT / BIN / CONTINUATION
Tff7b72if Tb4b4b4(Te6edf3rx_opcode_ Tff7b72=Tff7b72= T79c0ff0x0Tb4b4b4) Tb4b4b4{
T8b949e// Continuation — must have a message in progress.
Tff7b72if Tb4b4b4(Te6edf3msg_opcode_ Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3send_closeTb4b4b4(Te6edf3CLOSE_PROTOTb4b4b4)Tb4b4b4; Tff7b72returnTb4b4b4;
Tb4b4b4}
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3rx_opcode_ Tff7b72=Tff7b72= T79c0ff0x1 Tff7b72|Tff7b72| Te6edf3rx_opcode_ Tff7b72=Tff7b72= T79c0ff0x2Tb4b4b4) Tb4b4b4{
T8b949e// New message — previous one must be finished.
Tff7b72if Tb4b4b4(Te6edf3msg_opcode_ Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3send_closeTb4b4b4(Te6edf3CLOSE_PROTOTb4b4b4)Tb4b4b4; Tff7b72returnTb4b4b4;
Tb4b4b4}
Te6edf3msg_opcode_ Tff7b72= Te6edf3rx_opcode_Tb4b4b4;
Te6edf3msg_len_ Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Reserved data opcode.
Te6edf3send_closeTb4b4b4(Te6edf3CLOSE_PROTOTb4b4b4)Tb4b4b4; Tff7b72returnTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Te6edf3rx_phase_ Tff7b72= Te6edf3RxPhaseTff7b72:Tff7b72:Te6edf3HEAD_1Tb4b4b4;
Tff7b72breakTb4b4b4;
Tb4b4b4}
Tff7b72case Tff7b72RxPhaseTff7b72:Tff7b72:Tff7b72HEAD_1Tb4b4b4: Tb4b4b4{
Te6edf3rx_masked_ Tff7b72= Tb4b4b4(Te6edf3byte Tff7b72& T79c0ff0x80Tb4b4b4) Tff7b72!Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72const Tffa657uint8_t Te6edf3l7 Tff7b72= Te6edf3byte Tff7b72& T79c0ff0x7fTb4b4b4;
T8b949e// RFC 6455 §5.3: client→server frames MUST be masked.
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3rx_masked_Tb4b4b4) Tb4b4b4{ Te6edf3send_closeTb4b4b4(Te6edf3CLOSE_PROTOTb4b4b4)Tb4b4b4; Tff7b72returnTb4b4b4; Tb4b4b4}
T8b949e// Control frames cap at 125-byte payloads.
Tff7b72if Tb4b4b4(Te6edf3rx_is_ctrl_ Tff7b72&Tff7b72& Te6edf3l7 Tff7b72>Tff7b72= T79c0ff126Tb4b4b4) Tb4b4b4{
Te6edf3send_closeTb4b4b4(Te6edf3CLOSE_PROTOTb4b4b4)Tb4b4b4; Tff7b72returnTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3l7 Tff7b72< T79c0ff126Tb4b4b4) Tb4b4b4{
Te6edf3rx_payload_total_ Tff7b72= Te6edf3l7Tb4b4b4;
Te6edf3rx_ext_remaining_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3rx_phase_ Tff7b72= Te6edf3RxPhaseTff7b72:Tff7b72:Te6edf3MASKTb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3l7 Tff7b72=Tff7b72= T79c0ff126Tb4b4b4) Tb4b4b4{
Te6edf3rx_payload_total_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3rx_ext_remaining_ Tff7b72= T79c0ff2Tb4b4b4;
Te6edf3rx_phase_ Tff7b72= Te6edf3RxPhaseTff7b72:Tff7b72:Te6edf3EXT_LENTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// 64-bit length — not accepted in this server.
Te6edf3send_closeTb4b4b4(Te6edf3CLOSE_TOOBIGTb4b4b4)Tb4b4b4; Tff7b72returnTb4b4b4;
Tb4b4b4}
Tff7b72breakTb4b4b4;
Tb4b4b4}
Tff7b72case Tff7b72RxPhaseTff7b72:Tff7b72:Tff7b72EXT_LENTb4b4b4:
Te6edf3rx_payload_total_ Tff7b72= Tb4b4b4(Te6edf3rx_payload_total_ Tff7b72<Tff7b72< T79c0ff8Tb4b4b4) Tff7b72| Te6edf3byteTb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72-Tff7b72-Te6edf3rx_ext_remaining_ Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
T8b949e// Ensure the *accumulated* message wouldn't overflow.
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3rx_is_ctrl_
Tff7b72&Tff7b72& Te6edf3msg_len_ Tff7b72+ Te6edf3rx_payload_total_ Tff7b72> Te6edf3PAYLOAD_CAPTb4b4b4) Tb4b4b4{
Te6edf3send_closeTb4b4b4(Te6edf3CLOSE_TOOBIGTb4b4b4)Tb4b4b4; Tff7b72returnTb4b4b4;
Tb4b4b4}
Te6edf3rx_phase_ Tff7b72= Te6edf3RxPhaseTff7b72:Tff7b72:Te6edf3MASKTb4b4b4;
Tb4b4b4}
Tff7b72breakTb4b4b4;
Tff7b72case Tff7b72RxPhaseTff7b72:Tff7b72:Tff7b72MASKTb4b4b4:
Te6edf3rx_mask_Tb4b4b4[Te6edf3rx_mask_pos_Tff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3byteTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3rx_mask_pos_ Tff7b72=Tff7b72= T79c0ff4Tb4b4b4) Tb4b4b4{
Te6edf3rx_mask_pos_ Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3rx_payload_total_ Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3dispatch_frameTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3state_ Tff7b72!Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3OPENTb4b4b4) Tff7b72returnTb4b4b4;
Te6edf3reset_frame_rxTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3rx_phase_ Tff7b72= Te6edf3RxPhaseTff7b72:Tff7b72:Te6edf3PAYLOADTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tff7b72breakTb4b4b4;
Tff7b72case Tff7b72RxPhaseTff7b72:Tff7b72:Tff7b72PAYLOADTb4b4b4: Tb4b4b4{
Tff7b72const Tffa657uint8_t Te6edf3unmasked Tff7b72= Te6edf3byte Tff7b72^ Te6edf3rx_mask_Tb4b4b4[Te6edf3rx_payload_pos_ Tff7b72& T79c0ff3Tb4b4b4]Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3rx_is_ctrl_Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3rx_payload_pos_ Tff7b72< Te6edf3CTRL_PAYLOAD_CAPTb4b4b4) Tb4b4b4{
Te6edf3ctrl_buf_Tb4b4b4[Te6edf3rx_payload_pos_Tb4b4b4] Tff7b72= Te6edf3unmaskedTb4b4b4;
Tb4b4b4}
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3msg_len_ Tff7b72< Te6edf3PAYLOAD_CAPTb4b4b4) Tb4b4b4{
Te6edf3payload_Tb4b4b4[Te6edf3msg_len_Tff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3unmaskedTb4b4b4;
Tb4b4b4}
Tff7b72+Tff7b72+Te6edf3rx_payload_pos_Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3rx_payload_pos_ Tff7b72>Tff7b72= Te6edf3rx_payload_total_Tb4b4b4) Tb4b4b4{
Te6edf3dispatch_frameTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3state_ Tff7b72!Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3OPENTb4b4b4) Tff7b72returnTb4b4b4;
Te6edf3reset_frame_rxTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72breakTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tffa657void Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3dispatch_frameTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3rx_is_ctrl_Tb4b4b4) Tb4b4b4{
Tff7b72switch Tb4b4b4(Te6edf3rx_opcode_Tb4b4b4) Tb4b4b4{
Tff7b72case T79c0ff0x8Tb4b4b4: T8b949e// CLOSE
Te6edf3send_closeTb4b4b4(Te6edf3CLOSE_NORMALTb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tff7b72case T79c0ff0x9Tb4b4b4: T8b949e// PING — echo as PONG
Te6edf3send_frameTb4b4b4(T79c0ff0xATb4b4b4, Te6edf3ctrl_buf_Tb4b4b4, Te6edf3rx_payload_total_Tb4b4b4)Tb4b4b4;
Tff7b72breakTb4b4b4;
Tff7b72case T79c0ff0xATb4b4b4: T8b949e// PONG — ignore
Tff7b72breakTb4b4b4;
Tff7b72defaultTff7b72:
Te6edf3send_closeTb4b4b4(Te6edf3CLOSE_PROTOTb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3rx_fin_Tb4b4b4) Tb4b4b4{
T8b949e// Final fragment of a data message — deliver.
Tff7b72if Tb4b4b4(Te6edf3on_message_Tb4b4b4) Tb4b4b4{
Te6edf3on_message_Tb4b4b4(Te6edf3payload_Tb4b4b4, Te6edf3msg_len_Tb4b4b4, Te6edf3msg_opcode_ Tff7b72=Tff7b72= T79c0ff0x1Tb4b4b4,
Te6edf3on_message_ctx_Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3msg_opcode_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3msg_len_ Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4}
T8b949e// Non-final data fragment: payload is already accumulated, wait for
T8b949e// more.
Tb4b4b4}
Tffa657bool Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3send_frameTb4b4b4(Tffa657uint8_t Te6edf3opcodeTb4b4b4,
Tff7b72const Tffa657uint8_tTff7b72* Te6edf3dataTb4b4b4, Tffa657size_t Te6edf3lenTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3client_Tb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return Tffa657falseTb4b4b4;
Tffa657uint8_t Te6edf3hdrTb4b4b4[T79c0ff4Tb4b4b4]Tb4b4b4;
Tffa657size_t Te6edf3hdr_len Tff7b72= T79c0ff2Tb4b4b4;
Te6edf3hdrTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72= T79c0ff0x80 Tff7b72| Tb4b4b4(Te6edf3opcode Tff7b72& T79c0ff0x0fTb4b4b4)Tb4b4b4; T8b949e// FIN=1, no masking server-side
Tff7b72if Tb4b4b4(Te6edf3len Tff7b72< T79c0ff126Tb4b4b4) Tb4b4b4{
Te6edf3hdrTb4b4b4[T79c0ff1Tb4b4b4] Tff7b72= Tff7b72static_castTff7b72<Tffa657uint8_tTff7b72>Tb4b4b4(Te6edf3lenTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3len Tff7b72<Tff7b72= T79c0ff0xFFFFTb4b4b4) Tb4b4b4{
Te6edf3hdrTb4b4b4[T79c0ff1Tb4b4b4] Tff7b72= T79c0ff126Tb4b4b4;
Te6edf3hdrTb4b4b4[T79c0ff2Tb4b4b4] Tff7b72= Tff7b72static_castTff7b72<Tffa657uint8_tTff7b72>Tb4b4b4(Te6edf3len Tff7b72>Tff7b72> T79c0ff8Tb4b4b4)Tb4b4b4;
Te6edf3hdrTb4b4b4[T79c0ff3Tb4b4b4] Tff7b72= Tff7b72static_castTff7b72<Tffa657uint8_tTff7b72>Tb4b4b4(Te6edf3lenTb4b4b4)Tb4b4b4;
Te6edf3hdr_len Tff7b72= T79c0ff4Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72return Tffa657falseTb4b4b4; T8b949e// not supported in this server
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3client_Tb4b4b4.Te6edf3writeTb4b4b4(Te6edf3hdrTb4b4b4, Te6edf3hdr_lenTb4b4b4) Tff7b72!Tff7b72= Te6edf3hdr_lenTb4b4b4) Tff7b72return Tffa657falseTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3len Tff7b72> T79c0ff0 Tff7b72&Tff7b72& Te6edf3client_Tb4b4b4.Te6edf3writeTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3lenTb4b4b4) Tff7b72!Tff7b72= Te6edf3lenTb4b4b4) Tff7b72return Tffa657falseTb4b4b4;
Tff7b72return Tffa657trueTb4b4b4;
Tb4b4b4}
Tffa657bool Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3send_textTb4b4b4(Tff7b72const Tffa657charTff7b72* Te6edf3textTb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3send_frameTb4b4b4(T79c0ff0x1Tb4b4b4, Tff7b72reinterpret_castTff7b72<Tff7b72const Tffa657uint8_tTff7b72*Tff7b72>Tb4b4b4(Te6edf3textTb4b4b4)Tb4b4b4,
Te6edf3stdTff7b72:Tff7b72:Te6edf3strlenTb4b4b4(Te6edf3textTb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tffa657bool Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3send_binaryTb4b4b4(Tff7b72const Tffa657uint8_tTff7b72* Te6edf3dataTb4b4b4, Tffa657size_t Te6edf3lenTb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3send_frameTb4b4b4(T79c0ff0x2Tb4b4b4, Te6edf3dataTb4b4b4, Te6edf3lenTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tffa657void Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3send_closeTb4b4b4(Tffa657uint16_t Te6edf3codeTb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3bodyTb4b4b4[T79c0ff2Tb4b4b4] Tff7b72= Tb4b4b4{ Tffa657uint8_tTb4b4b4(Te6edf3code Tff7b72>Tff7b72> T79c0ff8Tb4b4b4)Tb4b4b4, Tffa657uint8_tTb4b4b4(Te6edf3codeTb4b4b4) Tb4b4b4}Tb4b4b4;
Te6edf3send_frameTb4b4b4(T79c0ff0x8Tb4b4b4, Te6edf3bodyTb4b4b4, T79c0ff2Tb4b4b4)Tb4b4b4;
Te6edf3state_ Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3CLOSINGTb4b4b4;
Tb4b4b4}
Tffa657void Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3shutdownTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Drop the active client first so we log a clean disconnect.
Te6edf3teardownTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72if defined(PORTDUINO)
Tff7b72if Tb4b4b4(Te6edf3listen_fd_ Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72:Tff7b72:Te6edf3closeTb4b4b4(Te6edf3listen_fd_Tb4b4b4)Tb4b4b4;
Te6edf3listen_fd_ Tff7b72= T79c0ff-1Tb4b4b4;
Tb4b4b4}
Tff7b72#Tff7b72else
T8b949e// No portable WiFiServer::stop() on stock Arduino — the destructor
T8b949e// would handle it, but we're not destroying the object yet. ESP32 isn't
T8b949e// affected by the inherited-fd bug (no execv() in our embedded flow);
T8b949e// this is structural symmetry only.
Tff7b72#Tff7b72endif
Tb4b4b4}
Tffa657void Te6edf3WebSocketServerTff7b72:Tff7b72:Te6edf3teardownTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72#Tff7b72if defined(PORTDUINO)
T8b949e// Only log a "disconnect" if we ever reached a usable state — skip
T8b949e// handshake failures and idle wakeups to avoid noise.
Tff7b72if Tb4b4b4(Te6edf3state_ Tff7b72=Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3OPEN Tff7b72|Tff7b72| Te6edf3state_ Tff7b72=Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3CLOSINGTb4b4b4) Tb4b4b4{
Te6edf3stdTff7b72:Tff7b72:Te6edf3fprintfTb4b4b4(Te6edf3stderrTb4b4b4, Ta5d6ff"Ta5d6ff[ws] client disconnected (%s:%u)Tffea00\nTa5d6ff"Tb4b4b4,
Te6edf3inet_ntoaTb4b4b4(Te6edf3client_addr_Tb4b4b4.Te6edf3sin_addrTb4b4b4)Tb4b4b4,
Tb4b4b4(Tffa657unsignedTb4b4b4)Te6edf3ntohsTb4b4b4(Te6edf3client_addr_Tb4b4b4.Te6edf3sin_portTb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72#Tff7b72endif
Tff7b72if Tb4b4b4(Te6edf3client_Tb4b4b4) Te6edf3client_Tb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3state_ Tff7b72= Te6edf3StateTff7b72:Tff7b72:Te6edf3WAITINGTb4b4b4;
Te6edf3req_len_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3msg_opcode_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3msg_len_ Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3reset_frame_rxTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72#Tff7b72endif T8b949e// ENABLE_WEBSOCKETS && __has_include(<WiFi.h>)
Served by rngit 1.5.2 - Generated in 0.05s